在C++中,每个程序都需要用到几个变量在写程序前就应该知道,每个数组有几个元素也必须在写程序时就决定。有时在编程序时,我们并不知道需要多大的数组或需要多少变量,直到程序开始运行,根据某一个当前运行值才能决定。
(或者通过交互确定。)
在定义数组时,我们建议按最大的可能值定义数组,每次运行时使用数组的一部分元素,当元素个数变化不是很大时,这个方案是可行的;但如果元素个数的变化范围很大时,就太浪费空间了。
这个问题一个更好的解决方案就是动态变量机制。所谓动态变量是指:在写程序时无法确定它们的存在,只有当程序运行起来时,随着程序的运行,根据程序的需求动态产生和消亡的变量。由于动态变量不能在程序中定义,也就无法给它们取名字,因此动态变量的访问需要通过指向动态变量的指针变量来进行间接访问;
要使用动态变量,必须定义一个相应类型的指针,然后通过动态变量申请的功能向系统申请一块空间,将空间的地址存入该指针变量。这样就可以间接访问动态变量了。当程序运行结束时,系统会自动回收指针占用的空间,但并不回收指针指向的动态变量的空间,动态变量的空间需要程序员在程序中地释放。因此要实现等实现动态内存分配,系统必须提供以下3个功能。
1 定义指针变量;
2 动态申请空间;
3 动态回收空间;
int *p
p = new int
*p =20
C++的new将返回一个指针;
new表示请求分配内存,并不能保证分配请求总能得到满足,因为这取决于系统的状态以及内存资源的可用性。
指针的作用:
1 使用堆中的内存;
2 作为函数参数,指针作为参数时属于地址引用,能完成值引用所不能完成的功能。
3 访问类的数据成员和成员函数;
C/C++这样的语言比较接近早期抽象计算机数据的思维方式,指针其实就是我们抽象数据并且这个数据是聚合但是实际内存离散的胶合层产物。后来出的语言通过其他的方式来抽象数据表示,将指针这个概念隐藏了,显得友好了,但是其实他还在那。
首先要理解几个概念:
1 存储程序的概念;数据要存得进去,取得出来,还要考虑空间的效率和时间的效率;
2 数据存储涉及到一个地址的概念,但如果将真正的地址呈现在你面前,类似于一个身份证号码的纯数字的东西,不方便记忆,也不够直观,所以在编程语言中弄出了变量、常量等标识符的概念,都用一个用一定涵义的标识符(如变量名)来表示,变量名与地址的映射由编译器或解释器配合操作系统来处理。
3 复杂的程序必须涉及到复杂的数据结构,也就是一定要考虑批量的数据处理。那数据元素的关系需要考虑。而存储到内存中,其实内存空间的地址是存在线性关系的,但变量名去把这个线性关系给弄没了。
例如,一块内存连续存了10个数据,这10个数据在内存地址上肯定存在线性关系,你如果知道了第一个数据的变量名,如果才能通过线性关系存取其他9个变量的数据呢?
这其实就是数组的概念,数组下标就是对内存地址线性关系的映射。
new(delete)与malloc(free)的主要区别是除了分配与释放内存,还会自动调用构造函数与析构函数。对于数组的操作,需要使用如delete [] stringParr;
符号[]是告诉编译器,我这里需要释放的是一个整个数组的指针,这样编译器就会逐个释放每个元素的内存占用。
使用malloc函数需要指定内存分配的字节数并且不能初始化对象,New会自动调用对象的构造函数。delete会调用对象的destructor,而free不会调用对象的destructor.
//为简单变量动态分配内存,并作初始化 #include <iostream.h> int main() { int *p; p = new int(99); //动态分配内存,并将99作为初始化值赋给它 cout<< *p; delete p; return 0; }//new 不能初始化动态数组
p = new int(5);//申请单个空间,并赋默认值为5
q = new char[10]; //申请10个空间
向量用法类似于数组,但向量长度不固定。如需更大容量来存储更多元素,它的容量就会自动扩充。向量在
#include <vector>
using namespace std;
给定了Base_Type(基类型)的向量类要写成vector
vector
vector
在向量添加元素要用成员函数push_back,如下例所示:
v.push_back(42);
一个元素位置获得了它的第一个值之后(无论是通过push_back,还是通过构造函数来初始化),以后就可使用方括号记号法来访问那个元素位置,和访问普通数组元素一样。
向量在任何时候都有一个容量,即当前分配了内存的元素的数量。成员函数capacity()返回向量的容量。不要混淆向量的容量和长度。长度是向量中元素的个数,容量是实际分配了内存的元素的个数。容量通常大于长度,且肯定大于或等于长度。
一旦向量容量不够,并需要空间来容纳一个附加成员,容量就会自动增加。不同C++实现对每次增加的容量有不同规定,但通常都会超过马上就要用到的容量。一个常见的方案是倍增当前需要的容量。由于增加容量是一项复杂的任务,所以相较于频繁分配许多小的内存块,一次性分配一个较大的内存块显得更有效率。
向量的长度是指向量中的元素个数,容量是当前实际分配了内存的元素的个数。对于向量v,可分别用成员函数v.size()和v.capacity()判断其长度和容量。
一般可忽略向量的容量,这不会对程序行为产生任何影响。但如果必须考虑效率问题,可考虑自己管理容量,而不是接受每次都使容量倍增的默认行为。可用成员函数reserve来显式增大向量的容量。例如以下语句将容量设为至少32 个元素:
v.reserve(32);
以下语句将容量设为向量当前元素个数加10:
v.reserve(v.size() + 10);
动态数组是写程序时不指定长度的数组,它的长度在程序运行时确定。
普通数组声明时必须指定长度。但有的时候,除非程序实际运行,否则不好确定长度。例如,数组可能容纳了一个学生ID 列表,但程序每次运行时,班级的学生数都可能发生变化。沿用传统做法,就必须预估数组可能的最大长度,并希望那个长度足够大,能适应所有情况。但这样做有两个问题。首先,你估计的长度可能还是太小,造成程序不能适应所有情况。其次,由于数组可能包含许多未使用的位置,所以会浪费计算机内存。动态数组避免了所有这些问题。用动态数组容纳学生ID,可在程序运行时输入班级的学生数。然后,程序会创建刚好那么大的动态数组。动态数组用操作符new 创建。和许多人想象的不同,动态数组的创建和使用其实非常简单。由于数组变量其实就是指针变量,所以可以用操作符new 创建被用作数组的动态变量,并像使用普通数组那样使用动态数组变量。例如,以下语句创建一个动态数组变量,其中含有10 个double 类型的数组元素:
oduble* p;
p = new double[10];
由于程序马上就要终止,所以并不是真的需要这个delete 语句。但如果程序准备用动态变量做其他事情,就应该包括这个delete 语句,将动态数组占用的内存还给自由存储。为动态数组执行的delete 语句类似于以前介绍过的delete 语句,只是在动态数组的情况下,必须包括一对空的方括号,如下所示:
delete [] a;
方括号告诉C++要销毁的是动态数组变量,所以系统会检查数组的长度,并删除刚好那么多的索引变量。如省略方括号,相当于告诉计算机只销毁一个int 类型的变量。例如:
delete a;
虽然上述语句不合法,但大多数编译器都检测不到这个错误。ANSI C++标准规定,这种情况下发生的事情是“未定义”的,意味着编译器的作者可以做他觉得方便的任何事情——
怎样使用动态数组?
声明指针变量 指针变量指向内存中的动态数组,并被用作动态数组名称:double * a;
调用new 使用操作符new 创建一个动态数组:a = new double[arraySize];动态数组长度在方括号内给出。可用int 变量或其他int 表达式给出长度。arraySize 可以是值在运行才确定的int 变量。
和普通数组一样使用 指针变量(比如a)可以像普通数组那样使用。例如,可采用标准方式书写索引变量,比如a[0],a[1],等等。不能再为指针变量赋其他任何指针值。相反,它应该像数组变量那样使用。
调用delete [] 用完动态数组后使用delete、一对空的方括号和指针变量来销毁动态数组,将其占用的内存还给自由存储以便重用。例如:delete [] a;
一个数组是一段连续的内存空间的命名,一个按上述流程在堆中申请的也是一段连续的内存空间,并也被赋值给了一个指针变量。
指针是内存地址,所以通过对变量在计算机内存中的地址进行命名,指针提供了一种间接的变量命名方式。
动态变量是程序运行时创建(和销毁)的变量。
动态变量要占用计算机内存的一个特殊区域,这个区域称为自由存储。程序结束动态变量的使用后,应该将动态变量占用的内存还给自由存储,以便重用;这是用delete 语句来完成的。
动态数组是其长度在程序运行时确定的数组。动态数组被实现为数组类型的动态变量。
malloc是库函数,不在编译器控制范围之内;new是运算符,在编译器控制范围之内。
调用malloc时,从堆中申请内存;调用new时,从堆中申请内存并为内存调用构造函数。
Concrete classes – especially classes with small representations – are much like built-in types: we define them as local variables, access them using their names, copy them around, etc. Classes in class hierarchies are different: we tend to allocate them on the free store using new, and we access them through pointers or references.
Now each object is owned by a unique_ptr that will delete the object when it is no longer needed, that is, when its unique_ptr goes out of scope.
If a constructor acquires a resource, its class needs a destructor to release the resource;
Thus, the standard containers plus back_inserter()s eliminate the need to use error-prone, explicit C-style memory management using realloc().
Dynamic memory management in C programs can be extremely complicated and consequently prone to defects. Common programming defects related to memory management include initialization errors, failing to check return values, dereferencing null or invalid pointers, referencing freed memory, freeing the same memory multiple times, memory leaks, and zero-length allocations.
Initializing large blocks of memory can degrade performance and is not always necessary. The decision by the C standards committee to not require malloc() to initialize this memory reserves this decision for the programmer. If required, you can initialize memory using memset() or by calling calloc(), which zeros the memory. When calling calloc(), ensure that the arguments, when multiplied, do not wrap.
If you’ve been programming in an application language, chances are you’ve used an automatic memory manager, or a garbage collector. At runtime, programs create objects. Periodically, the garbage collector determines which objects are no longer required by the program and safely deallocates them. This approach frees the programmer from worrying about managing an object’s life cycle, but it incurs several costs, including runtime performance, and requires some powerful programming techniques like deterministic resource management. C++ takes a more efficient approach. The trade-off is that C++ programmers must have intimate knowledge of storage durations. It’s our job, not the garbage collector’s, to craft object lifetimes.
Because the compiler doesn’t typically clean up memory after an object is deleted, a subtle and potentially serious type of bug called a use after free can occur. If you delete an object and accidentally reuse it, your program might appear to function correctly because the deallocated memory might still contain reasonable values. In some situations, the problems don’t manifest until the program has been in production for a long time—or until a security researcher finds a way to exploit the bug and discloses it!
In practice, your program’s operating environment might clean up leaked resources for you. For example, if you’ve written user-mode code, modern operating systems will clean up the resources when the program exits. However, if you’ve written kernel code, those operating systems won’t clean up the resources. You’ll only reclaim them when the computer reboots.
二维动态数组与二维指针
int (*array1)[5] = new int[10][5]; int *array2 = new int[50]; // a 10x5 array flattened into a single array int **array = new int*[10]; // allocate an array of 10 int pointers — these are our rows for (int count = 0; count < 10; ++count) array[count] = new int[5]; // these are our columns for (int count = 0; count < 10; ++count) delete[] array[count]; delete[] array; // this needs to be done last
如果忘记delete,则nudePtr超出作用域时自动释放,
释放的是自身,其指向的动态内存却再也没有机会释放,造成动态内存泄漏
res2超出作用域时会自动在栈上释放,同时会调用析构函数,delete m_ptr,释放堆上内存
封装的指针(智能指针)在释放自身(栈)的同时释放了其成员指针指向的动态内存(堆内存)
裸指针释放自身(栈)的同时却让其指向的动态内存失去了释放的机会(如果没有delete)
delete的不是nudePtr本身(本身超出作用域会自动在栈上释放),
delete针对的是其指向的堆内存,其实本质上也没有delete,只是标识为可重新链接的堆内存。
内存泄漏,最怕的是异常流程引起的内存泄漏。
对于异常,最怕的是用户的数据还没有保存,程序导致关闭,这是非常不好的体验。
reallocation process
1 allocated memory;
2 copy string;
3 deallocated memory;
finally ,got a new address.